package com.Cychat.gateway.filter;

import com.Cychat.commons.utils.JWTUtil;
import com.Cychat.commons.utils.TokenEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;
import java.util.Date;

/**
 * WebSocket认证过滤器
 * 功能：
 * 1. 专门处理WebSocket连接请求（/api/chat/**）
 * 2. 从token中解析userId
 * 3. 将userId注入到WebSocket连接路径中
 * 4. 移除URL中的token参数，保证安全性
 * 5. 使用混合验证策略：JWT签名验证 + 黑名单检查
 */
@Slf4j
@Component
public class WebSocketAuthFilter implements GlobalFilter, Ordered {

    private final RedisTemplate<String, Object> redisTemplate;

    // 构造函数注入RedisTemplate
    public WebSocketAuthFilter(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 只处理WebSocket请求
        if (!isWebSocketRequest(request)) {
            return chain.filter(exchange);
        }

        // 只处理聊天相关的WebSocket连接
        if (!path.startsWith("/api/chat")) {
            log.info("非聊天WebSocket请求，直接放行: {}", path);
            return chain.filter(exchange);
        }

        log.info("WebSocketAuthFilter - 处理WebSocket连接: {}", path);

        // 获取token
        String token = extractToken(request);

        if (token == null || token.isEmpty()) {
            log.warn("WebSocket连接缺少token: {}", path);
            return unauthorizedResponse(exchange, "WebSocket连接缺少认证token");
        }

        // 混合验证策略：先进行JWT签名验证（快速过滤无效token）
        TokenEnum tokenStatus = JWTUtil.verify(token);

        // 对于签名无效的token，直接拒绝，无需查Redis
        if (tokenStatus == TokenEnum.TOKEN_BAD) {
            log.warn("WebSocket Token签名无效: {}", path);
            return unauthorizedResponse(exchange, "Token无效");
        }

        // 对于有效或过期的token，检查黑名单（只有通过签名验证的才查Redis）
        String tokenTid = JWTUtil.getJti(token);
        if (tokenTid != null && redisTemplate.hasKey("lastlist:jwt:" + tokenTid)) {
            log.warn("WebSocket Token已被登出，拒绝访问: {}", path);
            return unauthorizedResponse(exchange, "Token已失效");
        }

        String newToken = null;
        Long userId = null;

        if (tokenStatus == TokenEnum.TOKEN_EXPIRE) {
            log.warn("WebSocket Token已过期: {}", path);
            return unauthorizedResponse(exchange, "Token已过期");
        } else {
            // Token有效（TOKEN_SUCCESS），正常解析userId
            try {
                userId = JWTUtil.getuid(token);
                if (userId == null || userId <= 0) {
                    log.warn("Token中的userId无效: {}", userId);
                    return unauthorizedResponse(exchange, "无效的用户信息");
                }
            } catch (Exception e) {
                log.error("WebSocket Token解析失败", e);
                return unauthorizedResponse(exchange, "Token解析失败");
            }
        }

        // 验证userId
        if (userId == null || userId <= 0) {
            log.warn("无法获取有效的userId");
            return unauthorizedResponse(exchange, "无效的用户信息");
        }

        log.info("WebSocket Token验证成功，userId: {}", userId);

        // 修改请求路径，将userId注入到路径中
        // 将 /api/chat/connect 或 /api/chat 改为 /api/chat/{userId}
        URI newUri = UriComponentsBuilder.fromUri(request.getURI())
                .replacePath("/api/chat/" + userId)  // 设置新的路径
                .replaceQuery(null)  // 移除所有查询参数（包括token），提高安全性
                .build()
                .toUri();

        log.info("WebSocket路径修改: {} -> {}", request.getURI(), newUri);

        // 创建新的请求，注入userId到请求头（备用方案）
        ServerHttpRequest.Builder requestBuilder = request.mutate()
                .uri(newUri)
                .header("X-User-Id", String.valueOf(userId));

        // 如果token被刷新了，将新token添加到请求头（供后端服务使用）
        if (newToken != null) {
            requestBuilder.header("X-New-Token", newToken);
        }

        ServerHttpRequest modifiedRequest = requestBuilder.build();

        ServerWebExchange modifiedExchange = exchange.mutate()
                .request(modifiedRequest)
                .build();

        // 如果token被刷新，需要在响应头中添加新token
        if (newToken != null) {
            return chain.filter(modifiedExchange).then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                // 使用与登录时一致的响应头名称
                response.getHeaders().set("authorization", newToken);
                response.getHeaders().set("Access-Control-Expose-Headers", "authorization, retoken");
                log.info("新token已添加到响应头");
            }));
        }

        return chain.filter(modifiedExchange);
    }

    /**
     * 判断是否为WebSocket请求
     */
    private boolean isWebSocketRequest(ServerHttpRequest request) {
        String upgrade = request.getHeaders().getFirst("Upgrade");
        String connection = request.getHeaders().getFirst("Connection");

        return "websocket".equalsIgnoreCase(upgrade) ||
                (connection != null && connection.toLowerCase().contains("upgrade"));
    }

    /**
     * 从请求中提取token
     * 支持三种方式（按优先级）：
     * 1. Authorization头: Bearer {token}
     * 2. Sec-WebSocket-Protocol头: {token}（WebSocket特有）
     * 3. 请求参数: token={token}
     */
    private String extractToken(ServerHttpRequest request) {
        // 方式1: 从Authorization请求头获取
        String authHeader = request.getHeaders().getFirst("authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            log.info("从Authorization头获取token");
            return authHeader.substring(7);
        }

        // 方式2: 从Sec-WebSocket-Protocol获取（某些WebSocket客户端会使用这个）
        String protocol = request.getHeaders().getFirst("Sec-WebSocket-Protocol");
        if (protocol != null && !protocol.isEmpty()) {
            log.info("从Sec-WebSocket-Protocol头获取token");
            return protocol;
        }

        // 方式3: 从请求参数获取
        List<String> tokenParams = request.getQueryParams().get("token");
        if (tokenParams != null && !tokenParams.isEmpty()) {
            log.info("从URL参数获取token");
            return tokenParams.get(0);
        }

        return null;
    }

    /**
     * 返回401未授权响应
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        String body = String.format("{\"code\":401,\"msg\":\"%s\",\"data\":null}", message);
        return response.writeWith(Mono.just(response.bufferFactory().wrap(body.getBytes())));
    }

    /**
     * 设置过滤器优先级
     * 数值越小优先级越高
     * 这个过滤器优先级最高，确保在AuthGlobalFilter之前执行
     */
    @Override
    public int getOrder() {
        return -1;
    }
}
